package client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Scanner;

import decode.DecodeMessage;

import utils.Statistics;
import utils.Utils;

import exception.TrackerException;
import file.BufferMap;
import file.CoupleIpPort;
import file.File;
import file.SharedFiles;


/**
 * @author acidflow
 *
 */
public class ConnectionToTracker implements Runnable{

	private Socket sockToTracker;
	private InputStream inTracker;
	private OutputStream outTracker;
	private Client client;
	private Thread _thread;
	private ArrayList<File> _answerToLook;
	private String ip;
	private int port;
	private ClientServer _clientServer;

	/**
	 * Construit la connexion du client au tracker et lance announce
	 * @param ipTracker
	 * @param portTracker
	 * @param c
	 * @throws TrackerException
	 */
	public ConnectionToTracker(Client c, ClientServer cs) throws TrackerException{
		try {
			this.client = c;
			this.ip = client.clientConfig.getIpTracker();
			this.port = client.clientConfig.getPortTracker();
			_clientServer = cs;
			this.connectToTracker();
			//this.inTracker =  this.sockToTracker.getInputStream();
			//this.outTracker = this.sockToTracker.getOutputStream();
		
			_thread = new Thread(this, "Thread Ecoute commande Tracker");
			announceTracker();
			// Getfiles de myLeech pour reprendre le download
			_thread.start();


		} catch (TrackerException e) {
			e.printStackTrace();	
		}
	}
	private void connectToTracker(){
		try {
			this.sockToTracker = new Socket(this.ip, this.port);
			this.inTracker =  this.sockToTracker.getInputStream();
			this.outTracker = this.sockToTracker.getOutputStream();
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private void disconnectFromTracker(){
		try {
			this.inTracker.close();
			this.outTracker.close();
			this.sockToTracker.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	/**
	 * Annonce les fichiers partagés au tracker
	 * @throws TrackerException
	 */
	private void announceTracker() throws TrackerException{
		String message = "announce listen " + this.client.getPort() + " seed [" + Utils.seedListToString(client.getMySeed()) + "] leech [" + Utils.leechListToString(client.getMyLeech()) + "]";
		try {
			this.outTracker.write(message.getBytes());
			String ok = DecodeMessage.decodeMessageFromSocket(this.inTracker, "tracker");
			if(!ok.equals("ok")){
				throw new TrackerException("Bad or No response from Tracker : " + ok);
			}
			else{
				System.out.println(ok); 
			}			
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		disconnectFromTracker();
	}

	/**
	 * Recherche un fichier sur le tracker
	 * @param criteriaList une chaine de caractères correspondant aux critères voulus
	 */
	public ArrayList<File> searchOnTracker(String criteriaList){
		this.connectToTracker();
		String message = "look [" + criteriaList + "]";
		
		ArrayList<File> listedFiles = new ArrayList<File>();
		try {
			this.outTracker.write(message.getBytes());
			String str = DecodeMessage.decodeMessageFromSocket(this.inTracker, "tracker");
			if(str.matches("^list \\[.*\\]")){
				System.out.println(str);
				str = str.replace("]", "");
				String toParse = str.split("\\[")[1];
				String[] filesData = toParse.split(" ");
				for(int i = 0; i < filesData.length/4; i++){
					int start = i * 4;
					File f = new File();
					f.setName(filesData[start]);
					f.setSize(Integer.parseInt(filesData[start+1]));
					f.setPieceSize(Integer.parseInt(filesData[start+2]));
					f.setIdFile(filesData[start+3]);
					
					listedFiles.add(f);
				}
				return listedFiles;
			}
			else{
				System.out.println("Fichier indisponible");
				return null;
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		disconnectFromTracker();
		return listedFiles;
	}

	/**
	 * Récupère les pairs disposant du fichier représenté par idFile
	 * @param idFile la clé du fichier
	 * @throws TrackerException
	 */
	public void getPeers(String idFile) throws TrackerException{
		this.connectToTracker();
		String message = "getfile "+idFile;
		try {
			this.outTracker.write(message.getBytes());
			String ok = DecodeMessage.decodeMessageFromSocket(this.inTracker, "tracker");
			
			if(!ok.matches("^peers "+idFile+" \\[.*\\]")){
				throw new TrackerException("Bad or No response from Tracker :" +ok);
			}
			else{
				String ip; 
				int port;
				int i = 0;
				ok = ok.replaceAll("\\]", "");
				ok = ok.split("\\[")[1];
				String[] list = ok.split(" ");
				while(i < this.client.getMyLeech().size() && !this.client.getMyLeech().get(i).getIdFile().equals(idFile)){
					i++;
				}
				//TODO Utiliser les peers observables
				if(i < this.client.getMyLeech().size()){
					File f = this.client.getMyLeech().get(i);
					for (int j=0; j< list.length; j++){
						String[] tmp = list[j].split(":");
						ip = tmp[0];
						port = Utils.parsePortString(tmp[1]);
						f.getPeers().add(new CoupleIpPort(ip,port));
					}
				}
				else{
					int j = 0;
					while(j < _answerToLook.size() && !_answerToLook.get(j).getIdFile().equals(idFile)){
						j++;
					}
					System.err.println(_answerToLook);
					if(j < _answerToLook.size()){
						File f = _answerToLook.get(j);
						f.setBufferMap(new BufferMap((int) Math.ceil(((float) f.getSize()) / f.getPieceSize())));
						// Création du fichier sur le disque
						f.setPath(SharedFiles.createLeechFile(client.clientConfig.getLeechPath(), f.getName(), f.getSize(), (int) f.getPieceSize()));
						
						// Ajout des peers
						for (int k=0; k< list.length; k++){
							String[] tmp = list[k].split(":");
							ip = tmp[0];
							port = Utils.parsePortString(tmp[1]);
							//Vérification que le peer est pas déjà dans la liste
							if(!f.getPeers().contains(new CoupleIpPort(ip,port)))
								f.getPeers().add(new CoupleIpPort(ip,port));
						}
						// Ajout du leech au client qui va lancer les threads de connexion
						f.addObserver(client);
						client.getMyLeechObservable().add(f);
					}
					else{
						System.err.println("Erreur no looked file correspond with the request");
					}
					
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		disconnectFromTracker();
	}

	/**
	 * Update régulier du tracker
	 * @param seed les fichiers partagés étant complets
	 * @param leech les fichiers en cours de téléchargement
	 * @throws TrackerException
	 */
	public void updateTracker(ArrayList<File> seed, ArrayList<File> leech) throws TrackerException{
		this.connectToTracker();
		String message = "update seed [";
		message += Utils.getAllKeysFromFiles(seed);
		message += "] leech [";
		message += Utils.getAllKeysFromFiles(leech);
		message += "]";

		try {
			this.outTracker.write(message.getBytes());
			String ok = DecodeMessage.decodeMessageFromSocket(this.inTracker, "tracker");
			if(!ok.equals("ok")){
				throw new TrackerException("Bad or No response from Tracker : " + ok);
			}
			else{
				System.out.println(ok); 
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		disconnectFromTracker();
	}

	private void decodeAction(String str){
		if (str.matches("^look \\[.*\\]")){
			str = str.replaceAll("\\]", "");
			String criteria = str.split("\\[")[1];
			_answerToLook = this.searchOnTracker(criteria);
		}
		else if(str.matches("^getfile [a-f0-9]+")){
			String key = str.split(" ")[1];
			try {
				this.getPeers(key);
			} catch (TrackerException e) {
				e.printStackTrace();
			}
		}
		else if(str.matches("leechStats *")){
			Statistics.leechPercentage(client.getMyLeech());
		}
		else if(str.matches("connectedPeers *")){
			Statistics.listConnectedPeers(_clientServer.getConnectedPeersToMyServer() , client.getStringPeersImConnectedTo());
		}
		else{
			System.out.println("Commande possibles : ");
			System.out.println("look [$Criterion1 $Criterion2 …]");
			System.out.println("getfile $Key");
			System.out.println("leechStats");
			System.out.println("connectedPeers");
		}
	}

	@Override
	public void run() {
		String strbuff;
		Scanner br = new Scanner(System.in);
		while(true){
			strbuff = br.nextLine();
			this.decodeAction(new String(strbuff));	
		}
	}
}
